package sn.radius.client

import scala.collection.immutable.BitSet

/*
 * Copyright 2016 Red Hat, Inc. and/or its affiliates
 * and other contributors as indicated by the @author tags.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */


/* (PD) 2006 The Bitzi Corporation
 * Please see http://bitzi.com/publicdomain for more info.
 *
 * $Id: Base32.java,v 1.2 2006/07/14 04:58:39 gojomo Exp $
 */

/**
 * Base32 - encodes and decodes RFC3548 Base32 (see [http://www.faqs.org/rfcs/rfc3548.html] )
 *
 * @author Robert Kaye
 * @author Gordon Mohr
 */
object Base32 {
  private final val base32Chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"
  private final val base32Lookup = Array[Int]( 0xFF, 0xFF, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
    0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E,
    0x0F, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x01,
    0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10, 0x11, 0x12, 0x13, 0x14,
    0x15, 0x16, 0x17, 0x18, 0x19, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF )

  /**
   * Encodes byte array to Base32 String.
   *
   * @param bytes Bytes to encode.
   * @return Encoded byte array <code>bytes</code> as a String.
   *
   */
  def encode(bytes: Array[Byte]): String = {
    var i = 0
    var index = 0
    var digit = 0
    var currByte, nextByte = 0
    val base32 = new StringBuffer((bytes.length + 7) * 8 / 5)

    while (i < bytes.length) {
      currByte = if bytes(i) >= 0 then bytes(i) else bytes(i) + 256

      /* Is the current digit going to span a byte boundary? */
      if (index > 3) {
        if ((i + 1) < bytes.length) {
          nextByte = if bytes(i + 1) >= 0 then bytes(i + 1) else bytes(i + 1) + 256
        } else {
          nextByte = 0
        }

        digit = currByte & (0xFF >> index)
        index = (index + 5) % 8
        digit <<= index
        digit |= nextByte >> (8 - index)
        i += 1
      } else {
        digit = (currByte >> (8 - (index + 5))) & 0x1F
        index = (index + 5) % 8
        if (index == 0) i += 1
      }
      base32.append(base32Chars.charAt(digit));
    }

    base32.toString
  }

  /**
   * Decodes the given Base32 String to a raw byte array.
   *
   * @param base32
   * @return Decoded <code>base32</code> String as a raw byte array.
   */
  def decode(base32: String): String = {
    val quintets: Seq[Int] = base32.map(c => base32Chars.indexOf(c))
    println(s"quintets(${quintets.length}) = ${quintets.mkString("`", ", ", "`")}")
    val bits: Seq[Int] = quintets.flatMap(i => Seq((i>>4) & 1, (i>>3) & 1, (i>>2) & 1, (i>>1) & 1, (i>>0) & 1))
    println(s"bits(${bits.length}) = ${bits.mkString("`", ", ", "`")}")
    val decoded = bits.sliding(8, 8).map(bb => bb.foldLeft(0){(res, curr) => (res << 1) | curr}).filterNot(_ == 0).map(_.toByte).toArray
    println(s"Decoded bytes = ${decoded.mkString("`", ", ", "`")}")
    new String(decoded)
  }

  def main0(args: Array[String]): Unit = {
    //val encoded = "OJQWI43FMM5GE53JNRZW63Q" // radsec:bwilson
    val encoded = "NU2WU5LZOZUW6NJZGBLEWRSLOJATQOLRJVSTGV3UINTVGMLJOBSQ"
    val decoded = decode(encoded)
    println(s"Decoded text = `$decoded`")
    assert(decoded == "radsec:bwilson")
  }
}